<script setup lang='tsx'>
import { createNameSpace } from '@/utils/core/css';
import { Card, Table, type BaseTableCol, type BaseTableProps, Pagination, type PageInfo, type TdFormItemProps, type BaseTableColumns, type TableCol, type DragSortContext, type TableProps, type SelectOptions, type TableInstanceFunctions, type TdPaginationProps } from 'tdesign-vue-next';
import { computed, onMounted, ref, useSlots, useTemplateRef, watch } from 'vue';
import type { ProFormInstance, ProFormOption, ProFormProps } from '../ProForm/index.vue';
import ProForm from '../ProForm/index.vue';
import { ChevronDownIcon, ChevronUpIcon, MoveIcon } from 'tdesign-icons-vue-next';
import { isFunction } from 'lodash-es';

const COMPONENT_NAME = 'pro-table';

const proTableBEM = createNameSpace(COMPONENT_NAME);

const props = withDefaults(defineProps<ProTableProps>(), {
    rowKey: 'id',
    options: () => []
});

const tableRef = useTemplateRef<TableInstanceFunctions>('tableRef');

const proFormOptions = ref<ProFormOption[]>([]);

const proFormRef = useTemplateRef<ProFormInstance>('proFormRef');

const tableColumns = ref<BaseTableCol[]>([]);

const loading = ref(false);

const slotsKeyArr = ref<string[]>([]);

const selectData = defineModel<any>('selectData', {
    default: () => ({ values: undefined })
});

let formModelValue: any = {};

const page = ref({
    total: 0,
    pageNum: 1,
    pageSize: props?.pageProps?.pageSize || 10
});

const innerData = ref<any[]>([]);

const formData = ref<any>({});

const showMore = ref(false);

const slots = useSlots();

const emits = defineEmits(['update:data']);

export interface ProTableProps {
    options: ProTableOption[];
    data?: any[];
    request?: (data: any) => (ProTableResult | Promise<ProTableResult>);
    formProps?: Optional<ProFormProps, 'options'>;
    hideForm?: boolean;
    hidePage?: boolean;
    draggAble?: boolean;
    selectAble?: boolean;
    tableProps?: TableProps;
    rowKey?: string;
    selectType?: 'multiple' | 'single';
    pageProps?: TdPaginationProps;
}

export interface ProTableResult<T = any> {
    list: T[];
    total: number;
    pageSize?: number;
    pageNum?: number;
    pages?: number;
    [key: string]: any;
}

export interface ProTableOption<T = any> {
    key: string;
    label?: string;
    formLabel?: TdFormItemProps['label'];
    tableTitle?: BaseTableProps['columns'][number]['title'];
    span?: number;
    type?: ProFormOption['type'];
    data?: ProFormOption['data'];
    hideInSearch?: boolean | (() => boolean);
    hideInTable?: boolean | (() => boolean);
    tableProps?: BaseTableCol;
    formProps?: any;
    formSlots?: any;
    defaultValue?: any;
    edit?: TableCol['edit'];
    render?: (row: T, rowIndex: number) => any;
}

export interface ProTableInstance {
    getFormValue: () => any;
    reset: () => void;
    reload: () => void;
    validate: TableInstanceFunctions['validateTableData'],
    clearValidate: TableInstanceFunctions['clearValidateData'],
    getFormInstance: () => ProFormInstance,
    getTableInstance: () => TableInstanceFunctions
}

export interface ProTableRequest<T = any> {
    pageNum: number;
    pageSize: number;
    form: T;
    sort?: Record<string, 'asc' | 'desc'>
}

onMounted(() => {
    buildOptions();
    buildTableColumns();
    request(1, page.value.pageSize);
    slotsKeyArr.value = Object.keys(slots).map(item => (item.includes('table-') && item.startsWith('table-')) ? item.replace('table-', '') : item);
})

function buildOptions() {
    proFormOptions.value = props.options.filter(item => isFunction(item.hideInSearch) ? !item.hideInSearch() : !item.hideInSearch
    ).map((item, index) => {
        if (item.defaultValue) {
            formData.value[item.key] = item.defaultValue;
        }
        return {
            name: item.key,
            label: item.formLabel || item.label,
            type: item.type || 'input',
            data: item.data || [],
            gridProps: {
                colSpan: item.span || 6
            },
            props: {
                ...item.formProps
            },
            slots: {
                ...item.formSlots
            },
            hidden: showMore.value !== true && index >= 3
        }
    });
}

function buildTableColumns() {
    tableColumns.value = getColumnsProps(props.options.filter(item => isFunction(item.hideInTable) ? !item.hideInTable() : !item.hideInTable));
}

function getColumnsProps(tableOptions: ProTableOption[]) {
    const options: BaseTableColumns = tableOptions.map(item => {
        const option: TableCol = {
            colKey: item.key,
            title: item.tableTitle || item.label,
            ellipsis: item.tableProps?.ellipsis || false,
            ...item.tableProps,
        }
        if (item.render) {
            option.cell = (_h, { row, rowIndex }) => {
                return item.render!(row, rowIndex);
            }
        }
        if (item.edit) {
            option['edit'] = {
                ...item.edit,
                onEdited: (context) => {
                    if (item.edit!.onEdited) {
                        item.edit!.onEdited(context);
                    } else {
                        innerData.value[context.rowIndex] = context.newRowData as unknown as any;
                    }
                }
            }
        }
        return option;
    });
    initSelectTable(options);
    initDragTable(options);
    return options;
}

function initSelectTable(options: TableCol[]) {
    if (options.filter(item => item.colKey === 'row-select').length > 0) return
    if (props.selectAble) {
        options.unshift({
            colKey: 'row-select',
            type: props.selectType || 'multiple',
            width: 46,
        })
    }

    return options;
}

function initDragTable(options: TableCol[]) {
    if (options.filter(item => item.colKey === 'drag').length > 0) return
    if (props.draggAble) {
        options.unshift({
            colKey: 'drag',
            cell: () => (
                <span>
                    <MoveIcon />
                </span>
            ),
            width: 46,
        })
    }
}


function handleSetShowMore() {
    showMore.value = !showMore.value
    buildOptions();
}

async function request(pageNum: number, pageSize: number) {
    if (props.request) {
        try {
            loading.value = true
            const result = await props.request({
                pageNum,
                form: formModelValue,
                pageSize
            });
            innerData.value = result.list;
            page.value.pageNum = pageNum;
            page.value.pageSize = pageSize;
            page.value.total = result.total;
        } finally {
            loading.value = false;
        }
    } else {
        if (props.data) {
            innerData.value = props.data;
        }
    }
}

function handlePageChange(pageInfo: PageInfo) {
    request(pageInfo.current, pageInfo.pageSize);
}

async function handleSubmit(data: any) {
    formModelValue = data;
    request(1, page.value.pageSize);
}

function handleResetClick() {
    reset();
    request(1, page.value.pageSize)
}

function reset() {
    formModelValue = {};
    proFormRef.value?.reset();
}

function handleDragSort(params: DragSortContext<any>) {
    innerData.value = params.newData;
}

function handleSelectChange(values: string[], context: SelectOptions<any>) {
    selectData.value = {
        values,
        context
    }
}

const colSpan = computed(() => {
    let colSpanList: number[] = [];
    proFormOptions.value.filter(item => !item.hidden).forEach((item) => {
        if (item.gridProps.colSpan >= 24) {
            colSpanList = [];
        } else {
            colSpanList.push(item.hidden ? 0 : item.gridProps.colSpan);
        }
    })

    const result = colSpanList[colSpanList.length - 1] === 24 ? 24 : 24 - (colSpanList.reduce((pre, current) => {
        return pre + current
    }, 0) % 24);

    return result
})

defineOptions({
    name: 'ProTable',
    globalComponent: true
})

watch(() => props.options, () => {
    buildOptions();
    buildTableColumns();
}, { deep: true });

watch(() => props.data, (value) => {
    innerData.value = value;
}, { deep: true });


watch(innerData, (value) => {
    emits('update:data', value);
}, { deep: true });

defineExpose({
    getFormValue: () => {
        return proFormRef.value.getFormValue();
    },
    reset: () => {
        reset();
        request(page.value.pageNum, page.value.pageSize);
    },
    reload: () => {
        handleResetClick();
    },
    validate: () => {
        return tableRef.value?.validateTableData()
    },
    clearValidate: () => {
        return tableRef.value?.clearValidateData()
    },
    getFormInstance: () => proFormRef.value,
    getTableInstance: () => tableRef.value
})

</script>

<template>
    <div :class="COMPONENT_NAME">
        <Card v-if="props.hideForm !== true && proFormOptions.length !== 0" :bordered="false"
            :class="proTableBEM.e('form')">
            <template v-if="slots['pro-table-header']" #header>
                <slot name="pro-table-header"></slot>
            </template>
            <ProForm v-model="formData" ref="proFormRef" v-model:loading="loading" :submit="handleSubmit"
                :formProps="{ labelAlign: 'left', ...props?.formProps?.formProps }" :gap="{ x: 8, y: 4 }"
                :options="proFormOptions">
                <template v-for="key in slotsKeyArr" #[key]="args">
                    <slot :name="key" v-bind="{ ...args }"></slot>
                </template>

                <template #extra>
                    <GridItem style="display:flex; justify-content: flex-end;" :colSpan="colSpan">
                        <t-space>
                            <t-button type="submit" :loading="loading">查询</t-button>
                            <t-button @click="handleResetClick" type="reset" variant="outline">重置</t-button>
                            <t-button v-if="proFormOptions.length > 3" @click="handleSetShowMore" variant="text">
                                <div style="display:flex; align-items: center;">
                                    <span style="margin-right: 2px;">{{ showMore ? '收起' : '展开' }}</span>
                                    <ChevronUpIcon v-if="showMore" />
                                    <ChevronDownIcon v-else />
                                </div>
                            </t-button>
                        </t-space>
                    </GridItem>
                </template>
            </ProForm>
        </Card>

        <Card :bordered="false" :class="proTableBEM.e('body')">

            <template v-if="slots['pro-table-title']" #title>
                <slot name="pro-table-title"></slot>
            </template>

            <template v-if="slots['pro-table-actions']" #actions>
                <slot name="pro-table-actions"></slot>
            </template>
            <Table v-if="!slots['card']" ref="tableRef" :select-row-keys="selectData.values"
                @select-change="handleSelectChange" @drag-sort="handleDragSort"
                :drag-sort="(props.draggAble ? (props.tableProps?.dragSort || 'row') : undefined) as unknown as any"
                :data="innerData" :columns="tableColumns" v-bind="{ ...$attrs, ...props.tableProps }"
                :row-key="props.rowKey">
                <template v-for="key in slotsKeyArr" #[key]="args">
                    <slot :name="`table-${key}`" v-bind="{ ...args }"></slot>
                </template>
            </Table>

            <slot v-else name="card" :list="innerData"></slot>

            <slot name="footer-action"></slot>

            <div :class="proTableBEM.e('pagination')" v-if="props.hidePage !== true">
                <Pagination @change="handlePageChange" v-bind="props.pageProps" v-model="page.pageNum"
                    v-model:pageSize="page.pageSize" :total="page.total">
                </Pagination>
            </div>
        </Card>
    </div>
</template>

<style lang="scss">
.pro-table {
    &__form {
        margin-block-end: 16px;
    }

    &__pagination {
        margin-top: 40px;
    }
}
</style>